import {
    addIcon, App,
    MarkdownView,
    Plugin, PluginSettingTab, Setting, Editor, Notice
} from "obsidian";

interface PluginSettings {
    newLinkText: string,
    highlightBullet: string,
    toggleRegenerateFromDetail: boolean,
}

const DEFAULT_SETTINGS: PluginSettings = {
    newLinkText: "在网页显示高亮",
    highlightBullet: "▲",
    toggleRegenerateFromDetail: false,
}
const URL_HASH_PREFIX = '#~:~highlights=';
const urlHashPrefixRegex = new RegExp(`${URL_HASH_PREFIX}(.*?)(#|$)`)

//Add chevron-up-square icon from lucide for mobile toolbar (temporary until Obsidian updates to Lucide v0.130.0)
addIcon("chevron-up-square", `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up-square"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><polyline points="8,14 12,10 16,14"></polyline></svg>`);

export default class WebHighlighterLinkPlugin extends Plugin {
    settings: PluginSettings

    async onload() {
        await this.loadSettings();

        this.addCommand({
            id: "generate-web-highlighter-link",
            name: "Generate web highlighter link",
            icon: "plus-square",
            callback: () => {
                this.generateLink();
            },
        });

        this.addCommand({
            id: "refresh-web-highlighter-link",
            name: "Refresh web highlighter link",
            icon: "plus-square",
            callback: () => {
                this.refreshLink();
            },
        });

        // add settings tab
        this.addSettingTab(new SettingTab(this.app, this));
    }

    async loadSettings() {
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    }

    async saveSettings() {
        await this.saveData(this.settings);
    }
    /**
     * 取出中括号内的内容
     * @param text
     * @param left
     * @param right
     * @returns {string}
     */
    getHighlightStr(text:string, left:string, right:string) {
        let result = text
        if (!text)
            return result
        // let regex = /-,(.+?),-/g;
        let regex = new RegExp(`${left}([\\s\\S]*?)${right}`, 'g');
        let options = text.match(regex)
        if (options) {
            let option = options[0]
            if (option) {
                result = option.substring(left.length, option.length - right.length)
            }
        }
        return result
    }
    // uuid() {
    //     var s = [];
    //     var hexDigits = "0123456789abcdef";
    //     for (var i = 0; i < 36; i++) {
    //         s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    //     }
    //     s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
    //     s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
    //     s[8] = s[13] = s[18] = s[23] = "-";
    //
    //     var uuid = s.join("");
    //     return uuid;
    // }
    isEncoded(uri: string) {
        uri = uri || '';
        return uri !== decodeURIComponent(uri);
    }

    encodeURIWrapper(url: string) {
        if (this.isEncoded(url)) {
            return url;
        } else {
            url = url.trim().replaceAll("%", "%25").replaceAll('"', "22%").replaceAll("?", "%3F").replaceAll("[", "%5B")
                .replaceAll("\\", "%5C").replaceAll("]", "%5D").replaceAll("^", "%5E").replaceAll("　", "%E3%80%80");
            return url.trim().replaceAll("#", "%23").replaceAll(":", "%3A").replaceAll("(", "%28").replaceAll(")", "%29").replaceAll("@", "%40").replaceAll("\n", "%0A")
                .replaceAll("<", "%3C").replaceAll(">", "%3E").replaceAll(" ", "%20").replaceAll("/", "%2F").replaceAll("&", "%26");
        }
    }
    findLineNum(text: string, doc: Editor) {
        //search each line for footnote markers
        //for each, add their name, line number, and start index to FootnoteMarkerInfo
        // const doc = mdView.editor;
        // originUrlList.forEach(url => {
            for (let i = 0; i < doc.lineCount(); i++) {
                let theLine = doc.getLine(i);
                let lineMatch;
                if (theLine.indexOf(text.trim()) !== -1) {
                    console.log(`${text}: line num: ${i}`);
                    return i;
                }
            }
    }

    /*
    这个方法是用来针对每个标注的回链url进行刷新，如果在笔记中有更新过备注文字，则可以通过回链返回至网页高亮备注查看
     */
    async refreshLink() {
        const mdView = this.app.workspace.getActiveViewOfType(MarkdownView);

        if (!mdView) return false;
        if (mdView.editor == undefined) return false;
        let regexMarkdownLinkList = new RegExp(`\\[${this.settings.highlightBullet.trim()}\\](.|\n)*?\n > (.|\n)*?\n(?! ?>)`, 'g'); // \[▲\](.|\n)*?\n > (.|\n)*?\n(?! ?>) JM：匹配到不是"(空格)?>"这个token为止，修复必须要两个\n\n才能表示结束的问题
        let highlightMarkdownLinkList = mdView.data.match(regexMarkdownLinkList)

        if (highlightMarkdownLinkList) {
            const [baseUrlViewstateMapping, uuidHighlightViewstateMapping, baseUrlOriginUrlMapping] = this.buildMapping(highlightMarkdownLinkList);
            let newPage = mdView.data;
            for (let highlightMarkdownLink of highlightMarkdownLinkList) {
                const [baseUrl, viewstate, comments, highhlightItemLink, highlightItemText] = this.resolveMarkdownLink(highlightMarkdownLink);
                const [uuid] = viewstate.split(";;").slice(0, 1);
                const newViewstate = uuidHighlightViewstateMapping.get(uuid);
                console.log(newViewstate);
                let url = baseUrlOriginUrlMapping.get(baseUrl);
                if (!url) url = baseUrl; // JM：如果没有baseUrl <=> originUrl mapping则直接用baseUrl
                const link = `${url}${URL_HASH_PREFIX}${newViewstate}`;
                newPage = newPage.replace(highhlightItemLink, link);
                mdView.editor.setValue(newPage);
            }
            if (highlightMarkdownLinkList.length > 0) {
                new Notice(`refreshed ${highlightMarkdownLinkList.length} highlights.`)
            }
        } else {
            new Notice(`no highlights found.`)
        }
    }

    /*
    这个方法是用来生成一个总的url回链到网页，场景是有在一个笔记中有多个标注来自多个不同的网页情况
    可以用这个方法汇总成每个网页一个连接，连接中汇总多个标注的高亮和备注信息
     */
    async generateLink() {
        const mdView = this.app.workspace.getActiveViewOfType(MarkdownView);

        if (!mdView) return false;
        if (mdView.editor == undefined) return false;
        let regexHighlightMarkdownLinkList = new RegExp(`\\[${this.settings.highlightBullet.trim()}\\](.|\n)*?\n > (.|\n)*?(\n\n|$)`, 'g');
        let highlightMarkdownLinkList = mdView.data.match(regexHighlightMarkdownLinkList)

        // const baseUrlOriginUrlMapping = new Map();
        const [baseUrlViewstateMapping, uuidHighlightViewstateMapping, baseUrlOriginUrlMapping] = this.buildMapping(highlightMarkdownLinkList);
        // console.log(urlMapping);
        let regexGeneratedLink = new RegExp(`\\[${this.settings.newLinkText.trim()}\\]\\((.*?)\\)`, 'g');
        let cleanedText = mdView.editor.getValue().replaceAll(regexGeneratedLink, ""); // JM：清除原来生成的连接
        mdView.editor.setValue(cleanedText.trimEnd() + "\n");
        baseUrlViewstateMapping.forEach((viewstateList, key) => {
            const linkViestates = viewstateList.reduce((p, v) =>{
                return p + "__" + v;
            });
            let url = baseUrlOriginUrlMapping.get(key);
            if (!url) url = key; // JM：如果没有baseUrl <=> originUrl mapping则直接用baseUrl
            const link = `[${this.settings.newLinkText}](${url}${URL_HASH_PREFIX}${linkViestates})`;
            console.log(link);
            const lastLineNum = mdView.editor.lineCount() + 1;
            mdView.editor.setLine( lastLineNum + 1, "\n\n" + link.trim() + "\n"); // JM: +1是当前行，+1+1才是下一行
        });

        /*
        let originUrlRegex = new RegExp(`\\[${this.settings.originLinkText}\\]\\((.*?)\\)`);
        const pages = mdView.data.split(originUrlRegex).filter(item => { return item.trim() !== "" });
        for (let i=0; i<=pages.length-2; i=i+2){
            const originUrl = pages[i].trim();
            const lineNum = this.findLineNum(originUrl, mdView.editor);
            const page = pages[i+1].trim();
            const newUrlMarkdown = this.generateLinkByPage(originUrl, page);
            console.log(newUrlMarkdown);
            const originUrlMarkdown = `[${this.settings.originLinkText}](${originUrl})`;
            mdView.editor.setLine(lineNum, `${originUrlMarkdown} ${newUrlMarkdown}`);
        }
         */
    }

    resolveMarkdownLink(markdownLink: string): [string, string, string, string, string] {
        const tempList = markdownLink.split(/\n ?>/);
        const highlightItem = tempList[0].trim();
        const [highlightItemFullMatch, highhlightItemLink, highlightItemText] = Array.from(highlightItem.match(new RegExp(`\\[${this.settings.highlightBullet}\\]\\((.*?)\\)([\\s\\S]*)$`)));
        const [originUrl] = highhlightItemLink.split(new RegExp(URL_HASH_PREFIX)).slice(0,1); // JM：拿originUrl直接切，倒也无所谓
        const [baseUrl] = originUrl.split(/[#\?]/).slice(0,1);
        const comments = tempList.splice(1).reduce((pv, cv) => {
            return `${pv.trim()}\n${cv.trim()}`;
        });
        const viewstate = highhlightItemLink.match(urlHashPrefixRegex)[1]; // JM：拿viewstate要严谨，因此用正则去match
        return [baseUrl, viewstate, comments, highhlightItemLink, highlightItemText];
    }

    buildMapping(highlihtMarkdownLinkList: Array<string>): [Map<string, Array<string>>, Map<string, string>, Map<string, string>] {
        const baseUrlViewstateMapping = new Map();
        const uuidHighlightViewstateMapping = new Map();
        const baseUrlOriginUrlMapping = new Map();
        highlihtMarkdownLinkList.forEach(markdownLink => {
            const [baseUrl, viewstate, comments, highhlightItemLink, highlightItemText] = this.resolveMarkdownLink(markdownLink);

            if (viewstate.indexOf("*ver_simple*") !== -1) {
                // JM：simple的情况和原viewstate就没有任何关系，高亮部分和comments都支持从笔记中修改后反馈回链至网页
                // JM：当然好原viewstate还有一点关系，就是highlightColor和textColor还要取回来
                // const viewstate = highhlightItemLink.match(urlHashPrefixRegex)[1]; // JM：拿viewstate要严谨，因此用正则去match
                const [uuid] = viewstate.split(";;").slice(0,1);
                const [highlightStyle] = viewstate.split(";;").slice(2,3); // JM：highlightStyle包括了highlightColor和textColor
                // JM：simple不从viewstate取，因为是支持在ob中修改的。
                let text = highlightItemText.replace("==", "-,").replace("==", ",-");
                const simpleHighlightViewstate = `${uuid};;${this.encodeURIWrapper(text.trim())};;${highlightStyle.trim()};;*ver_simple*;;${this.encodeURIWrapper(comments.trim())}`;
                if (!baseUrlViewstateMapping.has(baseUrl)) {
                    baseUrlViewstateMapping.set(baseUrl, []);
                }
                // urlMapping.set(baseUrl, urlMapping.get(baseUrl).push(simpleHighlight));
                baseUrlViewstateMapping.get(baseUrl).push(simpleHighlightViewstate);
                uuidHighlightViewstateMapping.set(uuid, simpleHighlightViewstate);
                console.log(simpleHighlightViewstate);
            } else {
                // RegenerateFromDetail就是直接从原viewstate拿除了comments外的数据，用笔记中的新comments拼接出新的viewstate
                // const [viewstate] = highhlightItemLink.split(new RegExp(URL_HASH_PREFIX)).slice(-1);
                // const viewstate = highhlightItemLink.match(urlHashPrefixRegex)[1]; // JM：拿viewstate要严谨，因此用正则去match
                const [uuid] = viewstate.split(";;").slice(0,1);
                const viewstateWithoutComments = viewstate.split(";;").slice(0, -1).join(";;");
                const newViewstate = viewstateWithoutComments + ";;" + this.encodeURIWrapper(comments.trim());
                if (!baseUrlViewstateMapping.has(baseUrl)) {
                    baseUrlViewstateMapping.set(baseUrl, []);
                }
                // urlMapping.set(baseUrl, urlMapping.get(baseUrl).push(simpleHighlight));
                baseUrlViewstateMapping.get(baseUrl).push(newViewstate);
                uuidHighlightViewstateMapping.set(uuid, newViewstate);
                console.log(newViewstate);
            }
        });
        return [baseUrlViewstateMapping, uuidHighlightViewstateMapping, baseUrlOriginUrlMapping];
    }

    // generateLinkByPage(page: string, baseUrlOriginUrlMapping: Map<string, string>=null): [Map<string, Array<string>>, Map<string, string>] { // Map<baseUrl, Array<viewstate>>
    //     // const markdownText = this.getHighlightStr(page, this.settings.pageNoteDelimiter, this.settings.pageNoteDelimiter);
    //     //
    //     // const highlightDelimiterRegex = new RegExp(`[${this.settings.highlightDelimiter}]`);
    //     // const highlightLinkList = markdownText.split(highlightDelimiterRegex).filter(item => {return item.trim() !== ""});
    //     let regex = new RegExp(`\\[${this.settings.highlightBullet.trim()}\\](.|\n)*?\n > (.|\n)*?(\n\n|$)`, 'g');
    //     let highlightLinkList = page.match(regex)
    //     let hash = "";
    //     const baseUrlViewstateMapping = new Map();
    //     const uuidHighlightViewstateMapping = new Map();
    //     if (!highlightLinkList) return [baseUrlViewstateMapping, uuidHighlightViewstateMapping];
    //     highlightLinkList.forEach(item => {
    //         // const [highlightItem, comments] = item.split(/\n ?>/);
    //         const tempList = item.split(/\n ?>/);
    //         const highlightItem = tempList[0].trim();
    //         // const [highlightItemFullMatch, highhlightItemLink, highlightItemText] = Array.from(highlightItem.match(/\[▲\]\((.*?)\)([\s\S]*)$/));
    //         const [highlightItemFullMatch, highhlightItemLink, highlightItemText] = Array.from(highlightItem.match(new RegExp(`\\[${this.settings.highlightBullet}\\]\\((.*?)\\)([\\s\\S]*)$`)));
    //         // const [baseUrl] = highhlightItemLink.split(/[#\?]/).slice(0,1);
    //         const [originUrl] = highhlightItemLink.split(new RegExp(URL_HASH_PREFIX)).slice(0,1); // JM：拿originUrl直接切，倒也无所谓
    //         // const [originUrl] = highhlightItemLink.match(urlHashPrefixRegex)[1];
    //         const [baseUrl] = originUrl.split(/[#\?]/).slice(0,1);
    //         if (baseUrlOriginUrlMapping){
    //             // JM: 有些url或许query和hash不同，但本质上还是同一个baseUrl，这里简单处理，就用最后一个作为originUrl
    //             baseUrlOriginUrlMapping.set(baseUrl, originUrl);
    //         }
    //         const comments = tempList.splice(1).reduce((pv, cv) => {
    //             return `${pv.trim()}\n${cv.trim()}`;
    //         });
    //
    //         if (highhlightItemLink.indexOf("*ver_simple*") !== -1) {
    //             // JM：simple的情况和原viewstate就没有任何关系，高亮部分和comments都支持从笔记中修改后反馈回链至网页
    //             // JM：当然好原viewstate还有一点关系，就是highlightColor和textColor还要取回来
    //             const viewstate = highhlightItemLink.match(urlHashPrefixRegex)[1]; // JM：拿viewstate要严谨，因此用正则去match
    //             const [uuid] = viewstate.split(";;").slice(0,1);
    //             const [highlightStyle] = viewstate.split(";;").slice(2,3); // JM：highlightStyle包括了highlightColor和textColor
    //             // JM：simple不从viewstate取，因为是支持在ob中修改的。
    //             let text = highlightItemText.replace("==", "-,").replace("==", ",-");
    //             const simpleHighlightViewstate = `${uuid};;${this.encodeURIWrapper(text.trim())};;${highlightStyle.trim()};;*ver_simple*;;${this.encodeURIWrapper(comments.trim())}`;
    //             if (!baseUrlViewstateMapping.has(baseUrl)) {
    //                 baseUrlViewstateMapping.set(baseUrl, []);
    //             }
    //             // urlMapping.set(baseUrl, urlMapping.get(baseUrl).push(simpleHighlight));
    //             baseUrlViewstateMapping.get(baseUrl).push(simpleHighlightViewstate);
    //             uuidHighlightViewstateMapping.set(uuid, simpleHighlightViewstate);
    //             console.log(simpleHighlightViewstate);
    //         } else {
    //             // RegenerateFromDetail就是直接从原viewstate拿除了comments外的数据，用笔记中的新comments拼接出新的viewstate
    //             // const [viewstate] = highhlightItemLink.split(new RegExp(URL_HASH_PREFIX)).slice(-1);
    //             const viewstate = highhlightItemLink.match(urlHashPrefixRegex)[1]; // JM：拿viewstate要严谨，因此用正则去match
    //             const [uuid] = viewstate.split(";;").slice(0,1);
    //             const viewstateWithoutComments = viewstate.split(";;").slice(0, -1).join(";;");
    //             const newViewstate = viewstateWithoutComments + ";;" + this.encodeURIWrapper(comments.trim());
    //             if (!baseUrlViewstateMapping.has(baseUrl)) {
    //                 baseUrlViewstateMapping.set(baseUrl, []);
    //             }
    //             // urlMapping.set(baseUrl, urlMapping.get(baseUrl).push(simpleHighlight));
    //             baseUrlViewstateMapping.get(baseUrl).push(newViewstate);
    //             uuidHighlightViewstateMapping.set(uuid, newViewstate);
    //             console.log(newViewstate);
    //         }
    //     });
    //     return [baseUrlViewstateMapping, uuidHighlightViewstateMapping];
    // }
}

class SettingTab extends PluginSettingTab {
    plugin: WebHighlighterLinkPlugin;

    constructor(app: App, plugin: WebHighlighterLinkPlugin) {
        super(app, plugin);
        this.plugin = plugin;
    }

    display(): void {
        const {containerEl} = this;
        containerEl.empty();

        // new Setting(containerEl)
        //     .setName('Origin Link Text')
        //     .setDesc('Origin Link Text.')
        //     .addText(text => text
        //         .setPlaceholder('原文地址')
        //         .setValue(this.plugin.settings.originLinkText)
        //         .onChange(async (value) => {
        //                 this.plugin.settings.originLinkText = value;
        //                 await this.plugin.saveSettings();
        //             }
        //         ));
        new Setting(containerEl)
            .setName('New Link Text')
            .setDesc('New Link Text.')
            .addText(text => text
                .setPlaceholder('在网页显示高亮')
                .setValue(this.plugin.settings.newLinkText)
                .onChange(async (value) => {
                        this.plugin.settings.newLinkText = value;
                        await this.plugin.saveSettings();
                    }
                ));
        new Setting(containerEl)
            .setName('Highlight Bullet')
            .setDesc('Highlight Bullet.')
            .addText(text => text
                .setPlaceholder('▲')
                .setValue(this.plugin.settings.highlightBullet)
                .onChange(async (value) => {
                        this.plugin.settings.highlightBullet = value;
                        await this.plugin.saveSettings();
                    }
                ));
        new Setting(containerEl)
            .setName('Toggle Regenerate From Detail')
            .setDesc('Toggle Regenerate From Detail.')
            .addToggle(toggle => toggle
                .setValue(this.plugin.settings.toggleRegenerateFromDetail)
                .onChange(async (value) => {
                        this.plugin.settings.toggleRegenerateFromDetail = value;
                        await this.plugin.saveSettings();
                    }
                ));
    }
}